package com.rw.tool.util.io.file;

import com.rw.tool.util.io.FileIOUtil;
import com.rw.tool.util.io.file.exception.GetIdDBException;
import com.rw.tool.util.io.file.exception.GetPropDBException;
import com.rw.tool.util.io.file.exception.IdCountException;
import com.rw.tool.util.io.file.exception.ManyResultDBException;
import com.rw.tool.util.io.file.property.BeanParser;
import com.rw.tool.util.io.file.property.EmptyPropertyVisitor;
import com.rw.tool.util.io.file.property.FileBean;
import com.rw.tool.util.io.file.property.FileProperty;
import com.rw.tool.util.io.file.property.NewInstance;
import com.rw.tool.util.io.file.wrapper.FileQueryWrapper;
import com.rw.tool.util.string.StringFormatter;
import com.rw.tool.util.string.StringUtil;
import com.rw.tool.util.string.URLUtil;

import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * JavaBean与文件中的交互
 * 乞丐版的数据库，最低限度满足少量数据的存放与取出
 * 尽量使用索引查询数据，原因如下：
 * <p>
 * 效率测试：索引查询
 * 一万数据量，根据索引随机查询十万次，平均每次查询在4.7ms左右
 * 十万数据量，根据索引随机查询十万次，平均每次查询在3.9ms左右
 * 根据索引查询和数据量关系不大，总花费时间只和查询次数有关
 * <p>
 * 效率测试：非索引查询
 * 一千数据量，随机查询一千次，平均每次查询在74ms左右
 * 一万数据量，随机查询一百次，平均每次查询在644ms左右
 * 不根据索引查询，每次查询的时间随着数据量增加而线性增加
 */
public abstract class FileDB<T> {
    // 默认工作路径
    protected static final File DEFAULT_WORK_PATH = new File(FileIOUtil.getWorkPath(), "/fileDB");

    // 分割字符
    protected static final String SPLIT_CHAR = "^";

    protected final String suffix;

    protected final Class<T> tClass;

    protected final File workPath;

    protected final FileBean<T> fileBean;

    protected final NewInstance<T> newInstance;

    protected final boolean noId;

    public FileDB(File workPath, Class<T> tClass, BeanParser<T> beanParser, NewInstance<T> newInstance) {
        // 判断工作文件夹是否是文件
        if (workPath.isFile()) {
            String errorMsg = "workPath must be a directory! {}";
            throw new IllegalArgumentException(StringFormatter.format(errorMsg, workPath));
        }

        // 如果目录不存在，并且创建文件夹失败了
        if (!workPath.exists() && !workPath.mkdirs()) {
            String errorMsg = "mkdir failed! path: {}";
            throw new IllegalArgumentException(StringFormatter.format(errorMsg, workPath));
        }

        // 初始化基础属性
        this.suffix = "." + extraSuffix() + tClass.getSimpleName();
        this.tClass = tClass;
        this.workPath = workPath;
        this.newInstance = newInstance;

        // 解析属性
        this.fileBean = beanParser.getFileBean(tClass);

        // 如果没有id，则进行特殊处理
        if (this.fileBean.getIdList().isEmpty()) {
            this.noId = true;
            FileProperty<T> idProp = new FileProperty<>(StringUtil.EMPTY, true, new EmptyPropertyVisitor<>());
            this.fileBean.getIdList().add(idProp);
        } else {
            this.noId = false;
        }
    }

    public FileDB(File workPath, Class<T> tClass, BeanParser<T> beanParser) {
        this(workPath, tClass, beanParser, defaultNewInstance());
    }

    public FileDB(Class<T> tClass, BeanParser<T> beanParser, NewInstance<T> newInstance) {
        this(DEFAULT_WORK_PATH, tClass, beanParser, newInstance);
    }

    public FileDB(Class<T> tClass, BeanParser<T> beanParser) {
        this(DEFAULT_WORK_PATH, tClass, beanParser, defaultNewInstance());
    }

    /**
     * 额外的后缀
     *
     * @return 额外的后缀
     */
    public abstract String extraSuffix();

    /**
     * 默认的创建新对象的方法
     *
     * @param <T> javabean的类型
     * @return 创建出来的新对象
     */
    private static <T> NewInstance<T> defaultNewInstance() {
        return tClass -> {
            try {
                return tClass.newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                throw new GetPropDBException(e);
            }
        };
    }

    /**
     * 获得对象对应的正确的文件名
     *
     * @param bean 对象
     * @return 文件名字
     */
    public String getFileName(T bean) {
        // 获取idList
        List<String> allId = getAllIdWithIncrement(bean);
        if (allId.contains(null)) {
            throw new GetIdDBException("This bean must have all id fields!");
        }

        return allId.stream()
                .map(this::encode)
                .collect(Collectors.joining(SPLIT_CHAR)) + suffix;
    }

    /**
     * 根据id获得文件名
     *
     * @param id id
     * @return 文件名字
     */
    public String getFileNameById(Object id) {
        // 获取idList
        return encode(id) + suffix;
    }

    /**
     * 对id进行url编码
     */
    private String encode(Object id) {
        // 这个方法不会额外对*号编码，所以这里手动编码
        return URLUtil.encode(id.toString()).replace("*", "%2A");
    }

    /**
     * 根据属性列表，获取所有ID
     * 同时计算上自增的属性
     *
     * @param bean 对象
     * @return ID列表
     */
    private List<String> getAllIdWithIncrement(T bean) {
        List<String> idList = new ArrayList<>();

        for (FileProperty<T> idProp : this.fileBean.getIdList()) {
            idList.add(getIdWithIncrement(idProp, bean));
        }

        return idList;
    }

    private String getIdWithIncrement(FileProperty<T> idProp, T bean) {
        // 如果取出ID，有值
        Object prop = idProp.getPropertyVisitor().get(bean);
        if (prop != null) {
            return prop.toString();
        }

        // 如果没有值，则查看是否为自增
        if (!idProp.isAutoIncrement()) {
            return null;
        }

        // 获得自增之后的值
        Integer incrementValue = getIncrementValue(idProp);
        // 回填
        idProp.getPropertyVisitor().set(bean, incrementValue);
        return incrementValue.toString();
    }

    private Integer getIncrementValue(FileProperty<T> idProp) {
        File[] files = workPath.listFiles(file -> file.getName().endsWith(suffix));
        if (files == null) {
            return 0;
        }

        List<FileProperty<T>> idList = fileBean.getIdList();
        int idIndex = idList.indexOf(idProp);

        return Arrays.stream(files)
                .map(File::getName)
                .map(name -> name.substring(0, name.length() - suffix.length()))
                .map(name -> name.split(Pattern.quote(SPLIT_CHAR)))
                .map(idArr -> idArr[idIndex])
                .mapToInt(Integer::parseInt)
                .max()
                .orElse(-1) + 1;
    }

    /**
     * 根据查询条件，获取所有ID
     *
     * @param queryWrapper 对戏那个
     * @return ID列表
     */
    private List<String> getAllId(FileQueryWrapper queryWrapper) {
        List<String> idList = new ArrayList<>();

        for (FileProperty<T> idProp : this.fileBean.getIdList()) {
            String name = idProp.getName();
            Object prop = queryWrapper.getEQValue(name);
            if (prop == null) {
                idList.add(null);
            } else {
                idList.add(prop.toString());
            }
        }

        return idList;
    }

    /**
     * 根据属性列表，获取所有ID
     *
     * @param bean 对象
     * @return ID列表
     */
    private List<String> getAllId(T bean) {
        List<String> idList = new ArrayList<>();

        for (FileProperty<T> idProp : this.fileBean.getIdList()) {
            Object prop = idProp.getPropertyVisitor().get(bean);
            idList.add(prop == null ? null : prop.toString());
        }

        return idList;
    }

    /**
     * 获取Id正则表达式
     *
     * @return 正则表达式
     */
    private Pattern getFilenamePattern(List<String> idList) {
        String idRegex = idList.stream()
                .map(this::idToRegex)
                .collect(Collectors.joining(Pattern.quote(SPLIT_CHAR)));

        return Pattern.compile(idRegex + Pattern.quote(suffix));
    }

    private String idToRegex(String id) {
        if (id == null) {
            return ".*";
        }

        return Pattern.quote(encode(id));
    }

    /**
     * 根据file，反编译变成对象
     *
     * @param file 文件对象
     * @return 对象类型
     */
    public abstract T getBean(File file);

    /**
     * 把bean设置进文件
     *
     * @param bean 对象
     * @param file 文件对象
     */
    public abstract void setBean(File file, T bean);

    /**
     * 插入数据
     *
     * @param data 数据
     */
    public void insert(T data) {
        String fileName = getFileName(data);
        File file = new File(workPath, fileName);
        setBean(file, data);
    }

    /**
     * 插入所有数据
     *
     * @param dataList 数据
     */
    public void insertAll(Collection<T> dataList) {
        for (T data : dataList) {
            insert(data);
        }
    }

    /**
     * 根据ID删除数据
     * 要求ID注解有且仅有一个
     *
     * @param id id
     */
    public void deleteById(Object id) {
        assertOneId();
        deleteByFileName(getFileNameById(id));
    }

    /**
     * 如果对象拥有不止一个ID，则抛出异常
     */
    private void assertOneId() {
        if (noId || this.fileBean.getIdList().size() != 1) {
            throw new IdCountException("Java bean can only have one id!");
        }
    }

    /**
     * 根据条件删除数据
     *
     * @param queryWrapper 条件
     */
    public void delete(FileQueryWrapper queryWrapper) {
        if (queryWrapper == null) {
            deleteAll();
            return;
        }

        filterFilesById(queryWrapper)
                .stream()
                .filter(file -> queryWrapper.match(getBean(file)))
                .forEach(File::deleteOnExit);
    }

    /**
     * 删除所有数据
     */
    public void deleteAll() {
        File[] files = workPath.listFiles(file -> file.getName().endsWith(suffix));
        if (files == null) {
            return;
        }

        Arrays.stream(files).forEach(File::deleteOnExit);
    }

    private void deleteByFileName(String filename) {
        File[] files = workPath.listFiles(file -> StringUtil.equals(file.getName(), filename));
        if (files != null) {
            for (File file : files) {
                file.deleteOnExit();
            }
        }
    }

    /**
     * 根据ID更新数据
     *
     * @param queryWrapper 更新条件
     */
    public void update(FileQueryWrapper queryWrapper, T data) {
        if (data == null) {
            throw new NullPointerException("update data can not be null!");
        }

        // 查看ID字段是否有值
        boolean withId = !getAllId(data).stream().allMatch(Objects::isNull);
        List<T> oldDataList = selectList(queryWrapper);
        for (T oldData : oldDataList) {
            // 获得新值
            T newData = getNewData(oldData, data);

            // 修改ID字符需要另外编写一套逻辑，因为涉及到删除文件，加入文件
            if (withId) {
                new File(workPath, getFileName(oldData)).deleteOnExit();
            }

            // 直接插入
            insert(newData);
        }
    }

    /**
     * 根据更新值和旧值，计算出新值
     *
     * @param oldData    旧值
     * @param updateData 更新的值
     * @return 最新的值
     */
    private T getNewData(T oldData, T updateData) {
        T newData = newInstance.newInstance(tClass);
        List<FileProperty<T>> fileProperties = fileBean.getAllField();
        for (FileProperty<T> property : fileProperties) {
            // 获取更新值
            Object value = property.getPropertyVisitor().get(updateData);
            // 如果更新值为null，则说明不更新，取旧值
            if (value == null) {
                value = property.getPropertyVisitor().get(oldData);
            }

            // 设置进去
            property.getPropertyVisitor().set(newData, value);
        }
        return newData;
    }

    /**
     * 根据ID查询数据
     * 要求ID注解有且仅有一个
     *
     * @param id id
     * @return 查询出来的数据
     */
    public T selectById(Serializable id) {
        assertOneId();
        String filename = getFileNameById(id);
        File file = new File(workPath, filename);
        return getBean(file);
    }

    /**
     * 根据条件查询数据
     *
     * @param queryWrapper 查询条件
     * @return 查询出来的数据
     */
    public T selectOne(FileQueryWrapper queryWrapper) {
        List<T> resultList = selectList(queryWrapper);
        if (resultList.isEmpty()) {
            return null;
        } else if (resultList.size() > 1) {
            throw new ManyResultDBException("There are many results here, not just one!");
        }

        return resultList.get(0);
    }

    /**
     * 根据条件查询数据
     *
     * @param queryWrapper 查询条件
     * @return 查询出来的据
     */
    public List<T> selectList(FileQueryWrapper queryWrapper) {
        if (queryWrapper == null) {
            return selectAll();
        }

        return filterFilesById(queryWrapper)
                .stream()
                .map(this::getBean)
                .filter(queryWrapper::match)
                .collect(Collectors.toList());
    }

    /**
     * 根据queryWrapper里面的所有ID进行过滤
     */
    private List<File> filterFilesById(FileQueryWrapper queryWrapper) {
        List<String> idList = getAllId(queryWrapper);
        Pattern pattern = getFilenamePattern(idList);

        File[] files = workPath.listFiles(file -> pattern.matcher(file.getName()).find());
        if (files == null) {
            return new ArrayList<>();
        }

        return Arrays.asList(files);
    }

    /**
     * 查询所有数据
     *
     * @return 所有数据
     */
    public List<T> selectAll() {
        File[] files = workPath.listFiles(file -> file.getName().endsWith(suffix));
        if (files == null) {
            return new ArrayList<>();
        }

        return Arrays.stream(files).map(this::getBean).collect(Collectors.toList());
    }

    /**
     * 根据所有ID查询数据
     * 要求ID注解有且仅有一个
     *
     * @param idList id列表
     * @return 查询出来的数据
     */
    public List<T> selectList(Collection<? extends Serializable> idList) {
        return idList.stream()
                .map(this::selectById)
                .collect(Collectors.toList());
    }
}
